//////////////////////////////////////
// Fast Container Library           //
// MIT License - Copyright (c) 2012 //
//////////////////////////////////////

#pragma once

#include "dynamic_array.h"

FC_NAMESPACE_BEGIN


template <class T, class Allocator>
class dynamic_array3d : public dynamic_array<T, Allocator>
{
public:
	typedef dynamic_array<T, Allocator>					base_type;
	typedef dynamic_array3d<T, Allocator>				this_type;
	typedef typename base_type::allocator_type			allocator_type;
	typedef typename base_type::size_type				size_type;
	typedef typename base_type::value_type				value_type;
	typedef typename base_type::reference				reference;
	typedef typename base_type::const_reference			const_reference;
	typedef typename base_type::iterator				iterator;
	typedef typename base_type::const_iterator			const_iterator;

	using base_type::size;
	using base_type::empty;
	using base_type::data;
	using base_type::front;
	using base_type::back;
	using base_type::get_allocator;
	using base_type::set_allocator;
	using base_type::fill;

	using base_type::m_allocator;
	using base_type::m_data;
	using base_type::m_size;

	dynamic_array3d()
		: base_type(), m_x(0), m_y(0), m_z(0), m_d1(0)
		{
		}

	explicit dynamic_array3d( const allocator_type& alloc )
		: base_type(alloc), m_x(0), m_y(0), m_z(0), m_d1(0)
		{
		}

	explicit dynamic_array3d( size_t z, size_t y, size_t x, const T& value = T(), const allocator_type& alloc = allocator_type() )
		: base_type(x * y * z, value, alloc), m_x(x), m_y(y), m_z(z), m_d1(x * y)
		{
		}

	dynamic_array3d( const this_type& x )
		: base_type(x), m_x(x.m_x), m_y(x.m_y), m_z(x.m_z), m_d1(x.m_d1)
		{
		}

	reference		operator ()( size_t z, size_t y, size_t x )			{ return m_data[ offset(z, y, x) ]; }
	const_reference operator ()( size_t z, size_t y, size_t x ) const	{ return m_data[ offset(z, y, x) ]; }

	reference at( size_t z, size_t y, size_t x )
	{
		FC_ASSERT(y < m_y && x < m_x && z < m_z);
		return m_data[ offset(z, y, x) ];
	}

	const_reference at( size_t z, size_t y, size_t x ) const
	{
		FC_ASSERT(y < m_y && x < m_x && z < m_z);
		return m_data[ offset(z, y, x) ];
	}

	iterator		iterator_at( size_t z, size_t y, size_t x )			{ return m_data + offset(z, y, x); }
	const_iterator	iterator_at( size_t z, size_t y, size_t x ) const	{ return m_data + offset(z, y, x); }

	FC_FORCE_INLINE size_t offset( size_t z, size_t y, size_t x ) const { return (x + y * m_x) + z * m_d1; }

	size_t x() const { return m_x; }
	size_t y() const { return m_y; }
	size_t z() const { return m_z; }

	void resize( size_t z, size_t y, size_t x, const T& value )
	{
		m_x = x;
		m_y = y;
		m_z = z;
		m_d1 = x * y;
		base_type::resize(x * y * z, value);
	}

	void resize( size_t z, size_t y, size_t x )
	{
		const size_t n = x * y * z;
		T* ptr = 0;

		if( n > 0 )
		{
			ptr = (T*)base_type::allocate(n);
			if( m_data )
			{
				const size_t width = fc::min(x, m_x);
				const size_t height = fc::min(y, m_y);
				const size_t depth = fc::min(z, m_z);

				//resizes an array while maintaining previous dimension-ordered data.
				for( size_t j(0); j < depth; ++j )
					for( size_t i(0); i < height; ++i )
					{
						const_iterator it = iterator_at(j, i, 0);
						fc::copy( it, it + width, ptr + ((i * width) + (j * width * height)) );
					}
			}
		}

		base_type::deallocate();
		m_data = ptr;
		m_size = n;
		m_x = x;
		m_y = y;
		m_z = z;
		m_d1 = x * y;
	}

	void write_region( size_t x, size_t y, size_t z, size_t width, size_t height, size_t depth, const this_type& region_to_copy )
	{
		FC_ASSERT(x <= m_x);
		FC_ASSERT(y <= m_y);
		FC_ASSERT(z <= m_z);
		FC_ASSERT(width <= region_to_copy.x());
		FC_ASSERT(height <= region_to_copy.y());
		FC_ASSERT(depth <= region_to_copy.z());

		if( x + width > m_x ) width = m_x - x;
		if( y + height > m_y ) height = m_y - y;
		if( z + depth > m_z ) depth = m_z - z;

		for( size_t j(0); j < depth; ++j )
			for( size_t i(0); i < height; ++i )
			{
				const_iterator it = region_to_copy.iterator_at(j, i, 0);
				fc::copy(it, it + width, iterator_at(z + j, y + i, x));
			}
	}

	void copy_region( size_t x, size_t y, size_t z, size_t width, size_t height, size_t depth, this_type& out_region ) const
	{
		FC_ASSERT(x + width < m_x);
		FC_ASSERT(y + height < m_y);
		FC_ASSERT(z + depth < m_z);

		if(out_region.x() < width || out_region.y() < height || out_region.z() < depth)
			out_region.resize(depth, height, width);

		for( size_t j(0); j < depth; ++j )
			for( size_t i(0); i < height; ++i )
			{
				const_iterator it = iterator_at(z + j, y + i, x);
				fc::copy(it, it + width, out_region.iterator_at(j, i, 0));
			}
	}

	this_type subarray( size_t x, size_t y, size_t z, size_t width, size_t height, size_t depth ) const
	{
		this_type x;
		copy_region(x, y, z, width, height, depth, x);
		return x;
	}

	this_type &operator =( const this_type &x )
	{
		if( this != &x )
		{
			m_x = x.m_x;
			m_y = x.m_y;
			m_z = x.m_z;
			m_d1 = x.m_d1;
			base_type::operator =(x);
		}

		return *this;
	}

	void swap( this_type& x )
	{
		base_type::swap(x);
		fc::swap(m_x, x.m_x);
		fc::swap(m_y, x.m_y);
		fc::swap(m_z, x.m_z);
		fc::swap(m_d1, x.m_d1);
	}

protected:
	size_t m_x, m_y, m_z, m_d1;
};


template <class T, class Allocator> inline
	void swap( dynamic_array3d<T, Allocator>& a, dynamic_array3d<T, Allocator>& b )
	{
		a.swap(b);
	}

template <class T> inline
	bool operator ==( const dynamic_array3d<T>& a, const dynamic_array3d<T>& b )
	{
		return( &a == &b || (a.x() == b.x() && a.y() == b.y() && a.z() == b.z() && fc::equal(a.begin(), a.end(), b.begin())) );
	}

template <class T> inline
	bool operator !=( const dynamic_array3d<T>& a, const dynamic_array3d<T>& b )
	{
		return !(a == b);
	}



FC_NAMESPACE_END
